﻿using System;
using System.Drawing;
using System.Net;
using System.Net.Sockets;
using System.Reflection;

namespace PkwHCFramework.Communications
{
    public class ModbusTcp
    {
        /// <summary>
        /// 功能码映射关系
        /// </summary>
        public readonly Dictionary<string, byte> ReadFuncCodes = new Dictionary<string, byte>();
        public readonly Dictionary<string, byte> WriteFuncCodes = new Dictionary<string, byte>();

        //读报文
        byte[] _readbytes = new byte[]
        {
            0x00,
            0x01,
            0x00,
            0x00,
            0x00,
            0x06,
            0xFF,
            0x01,
            0x00,
            0x01,
            0x00,
            0x10
        };

        //写报文（前10位）
        byte[] _writebytes = new byte[]
        {
            0x00,
            0x01,
            0x00,
            0x00,
            0x00,
            0x06,
            0xFF,
            0x01,
            0x00,
            0x01,
        };

        private DeviceLink _link;

        private List<RegisterPoint> _registers;

        private TcpClient _tcpClient;

        private int _timeSpan = 100; // 1000/10 ms  1s==1000ms,然后切成10个片段循环，插空进行写入

        /// <summary>
        /// 连接状态
        /// </summary>
        public bool IsConnected => _tcpClient != null && _tcpClient.Connected;

        private bool IsMonitored = false;

        public event Action<RegisterPoint, object> ValueUpdated;

        /// <summary>
        /// 写入队列
        /// </summary>
        private Queue<RegisterPoint> _writeQueue = new Queue<RegisterPoint>();

        private Socket _client;

        public ModbusTcp(DeviceLink link)
        {
            if (link == null)
            {
                throw new ArgumentNullException("传入配置为空，无法初始化");
            }
            _link = link;
            _timeSpan = (int)(link.AcqTimeSpan * 100);
            _registers = link.Points;
            _tcpClient = new TcpClient();
            Init();
        }

        void Init()
        {
            ReadFuncCodes.Add("Coil", 1);
            ReadFuncCodes.Add("DiscreteInput", 2);
            ReadFuncCodes.Add("HoldingRegister", 3);
            ReadFuncCodes.Add("InputRegister", 4);

            WriteFuncCodes.Add("Coil", 5);
            WriteFuncCodes.Add("HoldingRegister", 16);
            //修改从站地址
            _readbytes[6] = Convert.ToByte(_link.SlaveID);
            _writebytes[6] = Convert.ToByte(_link.SlaveID);
        }

        /// <summary>
        /// 连接
        /// </summary>
        private void Connect()
        {
            try
            {
                _tcpClient.Connect(IPAddress.Parse(_link.Ip), _link.Port);
                _client = _tcpClient.Client;
            }
            catch (Exception ex) { }
        }

        /// <summary>
        /// 启动主采集线程，循环采集
        /// </summary>
        public void DoMonitor()
        {
            _link.Comm = this;
            Task.Run(() =>
            {
                //防止重复启动
                if (IsMonitored)
                    return;
                IsMonitored = true;

                while (true)
                {
                    if (!_tcpClient.Connected)
                    {
                        Connect();
                    }
                    else
                    {
                        try
                        {
                            foreach (RegisterPoint point in _registers)
                            {
                                SetSendBytes(point);

                                //发送读报文
                                _client.Send(_readbytes);

                                int len = ReceviceDataLength(point.Length);

                                var errLen = 9;
                                var normalLen = 9 + len;
                                //接收报文
                                byte[] recvBytes = Recevice(normalLen, errLen);

                                //提取数据部分
                                byte[] dataBytes = new byte[len];
                                Array.Copy(recvBytes, 9, dataBytes, 0, len);
                                //数据处理
                                DealData(point, dataBytes);
                            }
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine(ex.Message);
                        }
                    }
                    //循环10次间隔，如果写入队列不为空，则执行写入
                    for (int i = 0; i < 10; i++)
                    {
                        while (_writeQueue.Count > 0)
                        {
                            var point = _writeQueue.Dequeue();
                            WriteData(point);
                        }
                        Task.Delay(_timeSpan).Wait();
                    }
                }
            });
        }

        /// <summary>
        /// 返回报文数据长度
        /// </summary>
        /// <param name="pointLength"></param>
        /// <returns></returns>
        private int ReceviceDataLength(int pointLength)
        {
            int len;
            if (_readbytes[7] <= 2)
            {
                len = pointLength / 8 + 1;
            }
            else
            {
                len = pointLength * 2;
            }
            return len;
        }

        /// <summary>
        /// 设置查询发送报文
        /// </summary>
        /// <param name="point"></param>
        private void SetSendBytes(RegisterPoint point)
        {
            //修改功能码
            _readbytes[7] = ReadFuncCodes[point.RegisterType];

            //修改起始地址
            var addressBytes = BitConverter.GetBytes(point.Address);
            _readbytes[8] = addressBytes[1];
            _readbytes[9] = addressBytes[0];

            //修改读寄存器数量
            var LengthBytes = BitConverter.GetBytes(point.Length);
            _readbytes[10] = LengthBytes[1];
            _readbytes[11] = LengthBytes[0];
        }

        /// <summary>
        /// 数据处理方法
        /// </summary>
        /// <param name="point"></param>
        /// <param name="bytes"></param>
        void DealData(RegisterPoint point, byte[] bytes)
        {
            //处理返回数据
            var funcCode = ReadFuncCodes[point.RegisterType];
            object value;

            if (funcCode <= 2)
            {
                //单点查询，线圈只查询一个，byte中不等于0，就是true
                value = bytes[0] != 0;
            }
            else
            {
                if (point.Type == typeof(byte))
                {
                    value = bytes[1];
                }
                else
                {
                    //字节序处理
                    byte[] newbytes = new byte[point.Length * 2];

                    //优化
                    for (int i = 0; i < point.Length; i++)
                    {
                        newbytes[2 * i] = bytes[2 * i + 1];
                        newbytes[2 * i + 1] = bytes[2 * i];
                    }

                    // 优化
                    var converterMothed = typeof(BitConverter).GetMethod(
                        "To" + point.Type.Name,
                        BindingFlags.Public | BindingFlags.Static,
                        new[] { typeof(byte[]), typeof(int) }
                    );
                    value = converterMothed.Invoke(null, new object[] { newbytes, 0 });
                }
            }
            //赋值
            if (!value.Equals(point.Value))
            {
                point.Value = value;
                ValueUpdated?.Invoke(point, value);
            }
        }

        /// <summary>
        /// modbustcp写入方法
        /// </summary>
        /// <param name="point"></param>
        private void WriteData(RegisterPoint point)
        {
            try
            {
                //00 01 00 00 00 06 FF 05 00 01 FF 00
                //00 05 00 00 00 06 FF 06 00 05 00 23
                //发送长度都一样，格式完全一样
                //前7位相同，第八位功能码不同，九、十位写入地址
                _writebytes[7] = WriteFuncCodes[point.RegisterType];
                //修改写入地址
                var addressBytes = BitConverter.GetBytes(point.Address);
                _writebytes[8] = addressBytes[1];
                _writebytes[9] = addressBytes[0];

                var writebuffer = _writebytes.ToList();
                if (point.RegisterType == "Coil")
                {
                    writebuffer[5] = 0x06;
                    if (point.WriteValue.Equals(true))
                    {
                        writebuffer.Add(0xFF);
                    }
                    else
                    {
                        writebuffer.Add(0x00);
                    }
                    writebuffer.Add(0x00);
                }
                else if (point.RegisterType == "HoldingRegister")
                {
                    byte dataLen = (byte)(point.Length * 2);
                    writebuffer[5] = Convert.ToByte(7 + dataLen);

                    //修改读寄存器数量
                    var LengthBytes = BitConverter.GetBytes(point.Length);
                    writebuffer.Add(LengthBytes[1]);
                    writebuffer.Add(LengthBytes[0]);
                    //添加数据长度
                    writebuffer.Add(dataLen);
                    var method = point.Type.GetMethod(
                        "Parse",
                        BindingFlags.Public | BindingFlags.Static,
                        new Type[] { typeof(string) }
                    );
                    object value = method.Invoke(
                        null,
                        new object[] { point.WriteValue.ToString() }
                    );
                    //转换成字节数组
                    var converterMothed = typeof(BitConverter).GetMethod(
                        "GetBytes",
                        BindingFlags.Public | BindingFlags.Static,
                        new[] { point.Type }
                    );

                    var valueBytes = (byte[])converterMothed.Invoke(null, new object[] { value });

                    //转字节序
                    byte[] newbytes = new byte[point.Length * 2];
                    for (int i = 0; i < point.Length; i++)
                    {
                        newbytes[2 * i] = valueBytes[2 * i + 1];
                        newbytes[2 * i + 1] = valueBytes[2 * i];
                    }
                    writebuffer.AddRange(newbytes);
                }
                var client = _tcpClient.Client;
                client.Send(writebuffer.ToArray());

                //接收报文，正常接收说明写入成功
                byte[] recvBytes = Recevice(12, 9);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        //写入值先加入一个队列
        public void Write(RegisterPoint point)
        {
            _writeQueue.Enqueue(point);
        }

        byte[] Recevice(int receiveLen, int errLen)
        {
            //接收报文
            byte[] recvBytes = new byte[receiveLen];

            //报文总长
            int recevTotalLength = 0;

            while (recevTotalLength < receiveLen && recevTotalLength != errLen)
            {
                try
                {
                    int needLength = receiveLen - recevTotalLength;
                    var recvLen = _client.Receive(
                        recvBytes,
                        recevTotalLength,
                        needLength,
                        SocketFlags.None
                    );
                    if (recvLen == 0)
                    {
                        throw new Exception("未接收到响应数据");
                    }
                    recevTotalLength += recvLen;
                }
                catch (Exception ex)
                {
                    throw new Exception("接收响应数据异常!" + ex.Message);
                }
            }
            if (recvBytes.Length == errLen)
            {
                throw new Exception("返回异常报文");
            }
            return recvBytes;
        }
    }
}
